
/**
  ******************************************************************************
  * Copyright 2021 The Microbee Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       storage_manager.c
  * @author     baiyang
  * @date       2021-8-8
  ******************************************************************************
  */

/*----------------------------------include-----------------------------------*/
#include "storage_manager.h"

#include <fcntl.h>
#include <stddef.h>
#include <unistd.h>
#include <stdio.h>

#include <common/microbee.h>
#include <common/console/console.h>
#include <internal_error/internal_error.h>
/*-----------------------------------macro------------------------------------*/
#ifndef MIN
#define MIN(a, b)         ((a) < (b) ? (a) : (b))
#endif
/*----------------------------------typedef-----------------------------------*/

/*---------------------------------prototype----------------------------------*/
#if !defined(RT_USING_FAL)
static void _flush_file(void* parameter);
#endif
/*----------------------------------variable----------------------------------*/
#if !defined(RT_USING_FAL)
static uint8_t buff_mission[STORAGE_MISSION_LENGTH];
static uint8_t buff_keys[STORAGE_KEYS_LENGTH];
#endif
/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
void StorageAccess_init(storage_access_t storage, StorageType type)
{
    storage->type = type;
    storage->total_size = 0;

#if defined(RT_USING_FAL)
    if (storage->type == StorageMission) {
        storage->storage_dev = fal_partition_find("mission");
    } else if (storage->type == StorageKeys){
        storage->storage_dev = fal_partition_find("signing_key");
    } else {
        storage->storage_dev = NULL;
    }

    if (storage->storage_dev != NULL) {
        storage->total_size = storage->storage_dev->len;
    }
#else
    storage->buff = NULL;
    storage->fd = -1;
    storage->dirty_mask = 0;

    if (storage->type == StorageMission) {
        storage->total_size = sizeof(buff_mission);
        storage->buff = &buff_mission[0];
    } else if (storage->type == StorageKeys){
        storage->total_size = sizeof(buff_keys);
        storage->buff = &buff_keys[0];
    }
#endif
}

/*
  base read function. The src offset is within the bytes allocated
  for the storage type of this StorageAccess object
*/
bool StorageAccess_read_block(const struct StorageAccess* storage, void *data, uint32_t addr, uint32_t n)
{
#if defined(RT_USING_FAL)
    if (storage->storage_dev == NULL) {
        return false;
    }

    int len = fal_partition_read(storage->storage_dev, addr, (uint8_t *)data, (size_t)n);

    return (len == n);
#else
    uint8_t *b = (uint8_t *)data;
    if (storage->buff != NULL && n > 0 && addr < storage->total_size) {
        const size_t n2 = MIN(n, storage->total_size - addr);
        memcpy(b, &storage->buff[addr], n2);
        n -= n2;
    }

    return (n == 0);
#endif
}


/*
  base read function. The addr offset is within the bytes allocated
  for the storage type of this StorageAccess object
*/
bool StorageAccess_write_block(struct StorageAccess* storage, uint32_t addr, const void *data, uint32_t n)
{
#if defined(RT_USING_FAL)
    if (storage->storage_dev == NULL) {
        return false;
    }

    int len = fal_partition_write(storage->storage_dev, addr, (const uint8_t *)data, (size_t)n);

    return (len == n);
#else
    if (storage->buff == NULL) {
        return false;
    }

    if(n <= 0)
    {
        return false;
    }
    
    const uint8_t *b = (const uint8_t *)data;

    if (n > 0 && addr < storage->total_size) {
        rt_mutex_take(&storage->_mutex, RT_WAITING_FOREVER);

        const size_t n2 = MIN(n, storage->total_size - addr);
        memcpy(&storage->buff[addr], b, n2);
        for (uint8_t i=addr/1024U; i<(addr+n2+1023U)/1024U; i++) {
            storage->dirty_mask |= (1ULL<<i);
        }
        n -= n2;

        rt_mutex_release(&storage->_mutex);
    }

    return (n == 0);
#endif
}

/*
  copy one area to another
 */
bool StorageAccess_copy_area(storage_access_t storage, const struct StorageAccess* source)
{
    // we deliberately allow for copies from smaller areas. This
    // allows for a partial backup region for parameters
    uint32_t total = MIN(StorageAccess_size(source), StorageAccess_size(storage));
    uint32_t ofs = 0;
    while (total > 0) {
        uint8_t block[32];
        uint32_t n = MIN(sizeof(block), total);
        if (!StorageAccess_read_block(source, block, ofs, n) ||
            !StorageAccess_write_block(storage, ofs, block, n)) {
            return false;
        }
        total -= n;
        ofs += n;
    }
    return true;
}

#if !defined(RT_USING_FAL)
bool StorageAccess_attach_file(storage_access_t storage, const char *filename)
{
    rt_err_t res = RT_EOK;

    const char* storage_type = NULL;
    switch (storage->type) {
    case StorageMission:
        storage_type = "stomis";
        break;
    default:
        storage_type = "sto";
        break;
    }

    res |= rt_mutex_init(&storage->_mutex, storage_type, RT_IPC_FLAG_FIFO);

    storage->fd = open(filename, O_RDWR | O_BINARY | O_CREAT);

    if (storage->fd < 0) {
        return false;
    }

    read(storage->fd, storage->buff, storage->total_size);

    rt_thread_t thread_ctx = rt_thread_create(storage_type, _flush_file, (void *)storage, 1024, PRIORITY_STORAGE_IO, 10);

    if (thread_ctx == NULL) {
        close(storage->fd);
        storage->fd = -1;
        return false;
    }

    res |= rt_thread_startup(thread_ctx);

    return res == RT_EOK;
}

static inline int storage_ffsll(uint64_t bits)
{
#if CONFIG_HAL_BOARD == HAL_BOARD_SITL_WIN
    uint16_t bit = 0;

    if (0 == bits)
        return 0;

    for (bit = 1; !(bits & 1); ++bit) {
        bits >>= 1;
    }

    return bit;
#else
    return __builtin_ffsll(bits);
#endif
}

/*
  flush file changes to microSD
 */
static void _flush_file(void* parameter)
{
    storage_access_t storage = (storage_access_t)parameter;

    while (true) {
        if (storage->fd < 0) {
            return;
        }

        if (storage->dirty_mask == 0) {
            rt_thread_mdelay(1000);
            continue;
        } else {
            rt_thread_mdelay(10);
        }

        rt_mutex_take(&storage->_mutex, RT_WAITING_FOREVER);

        // write out 1k at a time
        const int b = storage_ffsll(storage->dirty_mask);
        const off_t  ofs = (b-1)*1024;
        const uint32_t len = MIN(1024U, storage->total_size-ofs);
        bool io_fail = false;
        if (lseek(storage->fd, ofs, SEEK_SET) != ofs ||
            write(storage->fd, &storage->buff[ofs], len) != (int32_t)(len)) {
            io_fail = true;
        } else {
            storage->dirty_mask &= ~(1ULL<<(b-1));
        }
        if (storage->dirty_mask == 0) {
            if (fsync(storage->fd) != 0) {
                io_fail = true;
            }
        }
        if (io_fail) {
            INTERNAL_ERROR(internal_error_storage_fail);
        }

        rt_mutex_release(&storage->_mutex);
    }
}
#endif
/*------------------------------------test------------------------------------*/


